/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/


#ifndef MESSAGEREGISTERS_HPP
#define MESSAGEREGISTERS_HPP

#include <boost/shared_ptr.hpp>
#include <cassert>
#include <google/protobuf/message.h>
#include <google/protobuf/descriptor.h>
#include <map>

#include "HelpDefs.hpp"

namespace spelprog2
{

typedef google::protobuf::Message Message;
typedef boost::shared_ptr<Message> MessagePtr;
typedef boost::weak_ptr<Message> MessageWeakPtr;

const size_t MAX_MESSAGE_BODY_LEN = 0x10000;

class MessageTypeRegistry {
public:
    typedef unsigned TypeId;
    static const TypeId UNRECOGNIZED_TYPE = 0;
private:
    //TODO: assess log(n) performance penalty for not using unordered_map
    typedef std::map<const google::protobuf::Descriptor *, TypeId> DescToTypeMap;
    typedef std::map<TypeId, const Message*> TypeToMessageMap;
public:
    template <typename M>
    void registerMsg()
    {
        assert(!locked_);
        assert(descToType.find(M::descriptor()) == descToType.end());
        descToType[M::descriptor()] = nextTypeId;
        defaultMsgs[nextTypeId] = &M::default_instance();
        nextTypeId += 1;
    }

    void lock() { locked_ = true; }

    template <typename M>
    void registerErrorMsg()
    {
        assert(!locked_);
        errorMsg = &M::default_instance();
    }

    TypeId getMsgType(const Message & msg) const
    {
        DescToTypeMap::const_iterator it = descToType.find(msg.GetDescriptor());
        if (it == descToType.end()) {
            return UNRECOGNIZED_TYPE;
        }
        return it->second;
    }

    MessagePtr createMsg(TypeId type) const
    {
        TypeToMessageMap::const_iterator it = defaultMsgs.find(type);
        if (it == defaultMsgs.end()) {
            if (errorMsg) {
                return MessagePtr(errorMsg->New());
            }
            return MessagePtr();
        }
        return MessagePtr(it->second->New());
    }

    static MessageTypeRegistry & getInstance()
    {
        static MessageTypeRegistry inst;
        return inst;
    }
private:
    HIDE_EVIL_CONSTRUCTORS(MessageTypeRegistry);

    DescToTypeMap descToType;
    TypeToMessageMap defaultMsgs;
    TypeId nextTypeId;
    const Message * errorMsg;
    bool locked_;

    MessageTypeRegistry() : descToType(), defaultMsgs(),
        nextTypeId(UNRECOGNIZED_TYPE + 1), errorMsg(), locked_(false) { }
};


template <typename D>
struct MessageListener
{
    typedef boost::shared_ptr<MessageListener> Pointer;

    virtual ~MessageListener() { }
    virtual void onMessage(MessagePtr msg, const D & data) = 0;
};


template <typename D>
class MessageListenerRegistry
{
    typedef std::multimap<const google::protobuf::Descriptor *,
        typename MessageListener<D>::Pointer> DescHandlerMap;
    typedef std::list<typename MessageListener<D>::Pointer> HandlerList;

    DescHandlerMap descToHandler;
    HandlerList unhandled;
public:
    template <typename M>
    void addMsgListener(typename MessageListener<D>::Pointer handler)
    {
        descToHandler.insert(std::make_pair(M::descriptor(), handler));
    }

    void addUnhandledMsgListener(typename MessageListener<D>::Pointer handler)
    {
        unhandled.push_back(handler);
    }

    void removeMsgListener(typename MessageListener<D>::Pointer handler)
    {
        for (typename DescHandlerMap::iterator it = descToHandler.begin(), end = descToHandler.end(); it != end; ++it) {
            if (it->second == handler) {
                descToHandler.erase(it);
            }
        }
    }

    template <typename M>
    void removeMsgListenersOfType()
    {
        std::pair<typename DescHandlerMap::iterator, typename DescHandlerMap::iterator> range =
            descToHandler.equal_range(M::descriptor());

        descToHandler.erase(range.first, range.second);
    }

    bool hasListeners(const Message & msg) const
    {
        return descToHandler.find(msg.GetDescriptor()) != descToHandler.end();
    }

    void postMsg(MessagePtr msg, const D & data = D())
    {
        std::pair<typename DescHandlerMap::iterator, typename DescHandlerMap::iterator> range =
            descToHandler.equal_range(msg->GetDescriptor());
        
        bool handled = false;
        for (typename DescHandlerMap::iterator it = range.first; it != range.second; ++it) { //TODO: fix, iterator is not incrementable
            it->second->onMessage(msg, data);
            handled = true;
        }

        if (!handled) {
            for (typename HandlerList::iterator it = unhandled.begin(), end = unhandled.end(); it != end; ++it) {
                (*it)->onMessage(msg, data);
            }
        }
    }
};

}; //namespace spelprog2

#endif //MESSAGEREGISTERS_HPP
